import React, { useCallback, useEffect, useRef, useState } from 'react'

// 自定义 hook 模拟生命周期
// 注意自定义 hook 函数一定要用 use 开头
function useLife(name) {
  useEffect(() => {
    console.log(`${name}组件被创建`)
    return () => {
      console.log(`${name}组件被销毁`)
    }
  }, [name])
}

function Home() {
  useLife('Home')
  return <h4>这是 Home 组件</h4>
}

function App() {
  const [count, setCount] = useState(0)
  const helloRef = useRef()
  const inpRef = useRef()

  // useEffect
  useEffect(() => {
    document.title = count
  }, [count])

  const changeCount = (num) => {
    setCount(count + num)
  }

  // 使用 useRef
  const getDom = () => {
    console.log(helloRef.current)
    console.log(helloRef.current.outerText)
  }

  const getFocus = () => {
    console.log(inpRef.current)
    // 获取焦点
    inpRef.current.focus()
  }

  // 闭包陷阱
  const [num, setNum] = useState(0)
  const numRef = useRef()

  numRef.current = num
  const addNum = useCallback(() => {
    setNum(numRef.current + 1)
  }, [])
  // 而如果不用以上的，则会出现闭包陷阱，如下：
  // const addNum = useCallback(() => {
  //   setNum(num + 1)
  // }, [])

  // 自定义 hook 显示生命周期
  useLife('App')

  const [isShow, setIsShow] = useState(true)

  return (
    <div>
      {/* 
      函数式组件存在的缺陷：
      1、组件不会像 class 类组件那样被重新渲染：修改数据时，组件并不知道自己需要重新渲染
      2、如果组件页面重新渲染时，函数会被重新执行，第二次重新执行时，会重新给原本的数据 重新 赋原来定义的值，相当于不变
      3、不能写类似于生命周期的回调函数

      注意使用 hooks 的规则：
      1、只能在函数最外层调用 hook，不要在循环、条件或者子函数中使用
      2、只能在 React 的函数组件中调用 hook，不要在 js 的函数中调用
    */}
      {/* 
        useState:
        参数：唯一参数，初始化值，如果不设置则为 undefined
        返回值：数组，包含两个元素：
          元素一：当前状态的值（第一调用为初始化值）
          元素二：设置状态值的函数
      */}
      {/* 
        useEffect:
        可以通过该 hook，完成一些类似生命周期的功能：类似于网络请求、手动更新 DOM、事件的监听
        执行机制：当前传入的回调函数会在组件渲染完成之后，自动渲染

        但是当组件销毁时需要销毁数据（比如取消监听），如何通过 useEffect 实现：
        1、第一个参数是一个回调函数，但是这个回调函数，也可以 return 一个回调函数，在 return 的这个里面取消监听
          这是 effect 可选的清除机制，每个 effect 都可以返回一个清除函数
          组件在每次更新之前都会先进行清除
          如此可以将添加和移除订阅的逻辑放在一起
          他们都属于 effect 的一部分
          
        2、第一个参数是一个回调函数，第二个参数是一个数组
          第二个参数不传值的时候每次所有数据更新都执行，
          当只传入一个空数组的时候只在挂载和卸载的时候执行（相当于只执行一次），
          当传入一个值的时候就是当他更新执行（受谁影响）
      */}
      {/* 
        useContext:

      */}
      {/* 
        useReducer:

      */}
      {/* 
        useMemo 可以对回调函数返回值进行优化，useCallback 是对回调函数进行优化
      */}

      {/* 
        useRef:
        返回一个 ref 对象，返回的 ref 对象在组件的生命周期保持不变
        常用用法：
          1、引入 DOM（或者组件，但是需要是 class 组件）元素
          2、保存一个数据，这个对象在整个生命周期中可以保存不变
      */}
      {/* 
        自定义 hook 模拟生命周期
        注意：自定义 hook 名字一定要用 use 开头
      */}
      <h3>useState 和 useEffect</h3>
      <div>count：{count}</div>
      <button onClick={() => changeCount(1)}>+ 1</button>
      <button onClick={() => changeCount(-1)}>- 1</button>

      <h3>useRef</h3>
      <div ref={helloRef}>Hello World</div>
      <button onClick={getDom}>点击拿到上面的 DOM</button>
      <br />
      <input
        type="text"
        ref={inpRef}
      />
      <button onClick={getFocus}>点击使得输入框获取焦点</button>
      <br />
      <div>闭包陷阱：{num}</div>
      <button onClick={() => setNum(num + 1)}>原本加一</button>
      <button onClick={() => addNum()}>更改加一</button>

      <h3>自定义 hook 模拟生命周期</h3>
      <button onClick={() => setIsShow(!isShow)}>点击 隐藏/显示 Home 组件</button>
      {isShow && <Home />}
    </div>
  )
}

export default App
